using System;
using System.Linq.Expressions;
using System.Reflection;

namespace PowerfulExtensions.Helpers
{
    internal static class ExpressionHelper
    {
        public static TValue GetValue<TMember,TValue>(Expression<Func<TMember, TValue>> exp, TMember obj)
        {
            return (TValue)GetValue(exp.Body, obj);
        }

        private static object GetValue(Expression expression, object value)
        {
            object result;

            if (value == null)
                return null;

            switch (expression.NodeType)
            {
                case ExpressionType.Parameter:
                    return value;

                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression)expression;
                    result = GetValue(memberExpression.Expression, value);

                    return result == null ? null : GetValue(memberExpression.Member, result);

                case ExpressionType.Call:
                    var methodCallExpression = (MethodCallExpression)expression;

                    if (!SupportsMethod(methodCallExpression))
                        throw new NotSupportedException(methodCallExpression.Method + " is not supported");

                    result = GetValue(methodCallExpression.Method.IsStatic
                                          ? methodCallExpression.Arguments[0]
                                          : methodCallExpression.Object,
                                      value);
                    return result == null
                               ? null
                               : GetValue(methodCallExpression.Method, result);

                case ExpressionType.Convert:
                    var unaryExpression = (UnaryExpression)expression;

                    return Convert(unaryExpression.Type, GetValue(unaryExpression.Operand, value));

                default:
                    throw new MemberException();
            }
        }

        private static object Convert(Type type, object value)
        {
            return Expression.Lambda(Expression.Convert(Expression.Constant(value), type)).Compile().DynamicInvoke();
        }

        private static object GetValue(MemberInfo memberInfo, object instance)
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)memberInfo).GetValue(instance);
                case MemberTypes.Method:
                    return GetValue((MethodBase)memberInfo, instance);
                case MemberTypes.Property:
                    return GetValue((PropertyInfo)memberInfo, instance);
                default:
                    throw new MemberException();
            }
        }

        private static object GetValue(PropertyInfo propertyInfo, object instance)
        {
            return propertyInfo.GetGetMethod(true).Invoke(instance, null);
        }

        private static object GetValue(MethodBase method, object instance)
        {
            return method.IsStatic
                       ? method.Invoke(null, new[] { instance })
                       : method.Invoke(instance, null);
        }

        private static bool SupportsMethod(MethodCallExpression methodCallExpression)
        {
            return (methodCallExpression.Method.IsStatic && methodCallExpression.Arguments.Count == 1) || (methodCallExpression.Arguments.Count == 0);
        }

        private static MemberInfo GetMember(Expression exp)
        {
            if (exp is MemberExpression)
                return ((MemberExpression)exp).Member;
            else if (exp is UnaryExpression)
                return ((MemberExpression)(((UnaryExpression)exp).Operand)).Member;
            else
            {
                throw new MemberException();
            }
        }

        internal static MemberInfo GetMember<TMember>(Expression<Func<TMember>> exp)
        {
            return GetMember(exp.Body);
        }

        internal static MemberInfo GetMember<TClass, TMember>(Expression<Func<TClass, TMember>> exp)
        {
            return GetMember(exp.Body);
        }

    }
}
